package tss.server;

import java.io.IOException;
import java.net.*;
import java.nio.channels.*;
import java.util.*;

import tss.common.*;

public class Server extends Connection implements ServerInterface {

	private ArrayList<Extension> extensions;
	private Map<Integer, Extension> extensionNumbers;
	private Map<SocketAddress, Extension> extensionAddresses;
	private ArrayList<RouteDetail> routes;
	private Queue messageQueue = new LinkedList();
	
	public final int MAX_ROUTES = 5;
	public final int MAX_CONNECTIONS = 16;
	
	public Server(int port) throws IOException {
		notify("Initializing Server");
		
		// Initialize extensions
		extensions = new ArrayList<Extension>(MAX_CONNECTIONS);
		extensionNumbers = new HashMap<Integer, Extension>();
		
		for (int i = 0; i < MAX_CONNECTIONS; i++) {
			int extensionNumber = i + 1;
			Extension extension = new Extension(extensionNumber);
			extensions.add(extension);
			extensionNumbers.put(extensionNumber, extension);
		}

		// Initialized but empty initially
		extensionAddresses = new HashMap<SocketAddress, Extension>();
		
		// Initialize routes
		routes = new ArrayList<RouteDetail>();
		for (int i = 0; i < MAX_ROUTES; i++) {
			routes.add(null);
		}

		super.connectServer(port);
		
		notify("Ready to accept connections.");
	}

	@Override
	protected void onDisconnect(SelectionKey key) {
		SocketChannel channel = (SocketChannel)key.channel();
		SocketAddress address = channel.socket().getRemoteSocketAddress();
		
		Extension extension = extensionAddresses.get(address);
		if (extension != null) {
			if (extension.getRoute() != null) {
				RouteDetail route = extension.getRoute();
				Extension receiver = null;
				if (extension.getExtensionNumber() == route.getExtensionA()) {
					receiver = extensionNumbers.get(route.getExtensionB());
				} else {
					receiver = extensionNumbers.get(route.getExtensionA());
				}
				extension.setAvailableDisconnect();
				receiver.setAvailableDisconnect();

				routes.set(route.getRouteNumber(), null);
			}
			extension.setUnavailable();
			notify("DISCONNECT", "Extension disconnected: " + extension.getExtensionNumber(), address);
		} else {
			notify("DISCONNECT", "Extension not found.", address);
		}
	}
	
	protected void onConnect(SelectionKey key) throws Exception {
		throw new Exception("Not Supported");
	}

	protected void onAccept(SelectionKey key) throws Exception {
		
	}
	
	@Override
	protected void onRead(String packet, SelectionKey key) {
		SocketChannel channel = (SocketChannel)key.channel();
		SocketAddress address = channel.socket().getRemoteSocketAddress();

		String opt = null;
		String lineEnd =  System.getProperty("line.separator");	
		StringTokenizer tokens = new StringTokenizer(packet," "+lineEnd);
		
		opt = tokens.nextToken();
		
		if (!opt.equals(Protocol.MSG_LIST_ACCOUNTS))
			notify("Received Request: " + opt, address);
		
		// new extension register request
		if (opt.equals(Protocol.MSG_REGISTER_REQ)) { registerExtension(key, channel, address); }
		// list extensions
		else if (opt.equals(Protocol.MSG_LIST_ACCOUNTS)) { listExtensions(key, channel, address); }
		// dial number
		else if (opt.equals(Protocol.MSG_CALL_REQ)) { callExtension(tokens.nextToken(), key, channel, address); }
		
		else if (opt.equals(Protocol.MSG_CALL_ACCEPT)) { acceptConnection(key, channel, address); }
		
		else if (opt.equals(Protocol.MSG_CALL_DISCONNECT)) { disconnectConnection(key, channel, address); }
	}
	
	private void acceptConnection(SelectionKey key, SocketChannel channel, SocketAddress address) {
		Extension extension = extensionAddresses.get(address);
		if (extension != null && extension.getRoute() != null) {
			RouteDetail route = extension.getRoute();
			if (extension.getExtensionNumber() == route.getExtensionB()) {
				Extension receiver = null;
				if (extension.getExtensionNumber() == route.getExtensionA()) {
					receiver = extensionNumbers.get(route.getExtensionB());
				} else {
					receiver = extensionNumbers.get(route.getExtensionA());
				}
			
				notify("SEND", "Notify of connection accepted.", extension.getAddress());
				write(Protocol.MSG_CALL_TALKING, extension.getChannel());
				notify("SEND", "Notify of connection accepted.", receiver.getAddress());
				write(Protocol.MSG_CALL_TALKING, receiver.getChannel());
			}
		}
	}
	
	private void disconnectConnection(SelectionKey key, SocketChannel channel, SocketAddress address) {		
		Extension extension = extensionAddresses.get(address);
		if (extension != null) {
			notify("SEND", "Notify of connection disconnected.", extension.getAddress());
			write(Protocol.MSG_CALL_DISCONNECT, extension.getChannel());
	
			if (extension.getRoute() != null) {
				RouteDetail route = extension.getRoute();
				Extension receiver = null;
				if (extension.getExtensionNumber() == route.getExtensionA()) {
					receiver = extensionNumbers.get(route.getExtensionB());
				} else {
					receiver = extensionNumbers.get(route.getExtensionA());
				}
							
				notify("SEND", "Notify of connection disconnected.", receiver.getAddress());
				write(Protocol.MSG_CALL_DISCONNECT, receiver.getChannel());
				
				extension.setAvailableDisconnect();
				receiver.setAvailableDisconnect();
				
				routes.set(route.getRouteNumber(), null);
			}
		}
	}
		
	private void callExtension(String accNum, SelectionKey key, SocketChannel channel, SocketAddress address) {
		Integer receiverNumber = Integer.parseInt(accNum);
		Extension receiverExtension = extensionNumbers.get(receiverNumber);
		Extension senderExtension = extensionAddresses.get(address);
		
		// Check for available lines
		if (availableLines()) {			
			// check if receiver busy
			if (receiverExtension.getExtensionState() == ExtensionStates.Available) {
				if (assignLine(senderExtension, receiverExtension)) {
					notify("CALL", "Sender Ringing", senderExtension.getAddress());
					notify("CALL", "Receiver Ringing", receiverExtension.getAddress());
					
					write(Protocol.MSG_CALL_RING, key);
					write(Protocol.MSG_CALL_RING, receiverExtension.getChannel());
				}
			} else if (receiverExtension.getExtensionState() == ExtensionStates.Busy) {
				notify("CALL", "Receiver Busy", address);
				write(Protocol.MSG_CALL_BUSY, key);
			} else {
				notify("CALL", "Receiver Unavailable", address);
				write(Protocol.MSG_CALL_UNAVAILABLE, key);
			}
			return;
		}
		notify("CALL", "No Available Lines", address);
		write(Protocol.MSG_CALL_NOLINES, key);
	}
	
	private boolean availableLines() {
		for (int i = 0; i < MAX_ROUTES; i++) {
			if (routes.get(i) == null) {
				return true;
			}
		}
		return false;
	}
	
	private boolean assignLine(Extension sender, Extension receiver) {		
		for (int i = 0; i < MAX_ROUTES; i++) {
			if (routes.get(i) == null) {
				RouteDetail route = new RouteDetail(i, sender.getExtensionNumber(), receiver.getExtensionNumber());
				routes.set(i, route);
				
				sender.setBusy(route);
				receiver.setBusy(route);
		
				return true;
			}
		}
		
		return false;
	}
	

	private void registerExtension(SelectionKey key, SocketChannel channel, SocketAddress address) {
		// find open extension
		for(Extension a : extensions) {
			if (a.getExtensionState() == ExtensionStates.Unavailable) {
				a.setAvailable(channel);
				
				// Update Address
				if (extensionAddresses.containsKey(address))
					extensionAddresses.remove(address);
				extensionAddresses.put(address, a);
				
				notify("ACK User gets extension number: " + a.getExtensionNumber(), address);
				write(Protocol.MSG_REGISTER_ACK + " " + a.getExtensionNumber(), key);
				return;
			}
		}
		
		// no extension available
		notify("NACK No Extension Available", address);
		write(Protocol.MSG_REGISTER_NACK, key);
	}

	private void listExtensions(SelectionKey key, SocketChannel channel, SocketAddress address) {
		//notify("ACK LIST request.", address);
		write(Protocol.MSG_LIST_ACCOUNTS + makeList(), key);
	}
	
	public String makeList() {
		String list = "";
		if(extensions != null)
		{	
			for( Extension extension : extensions)
			{
				list += " " + extension.getExtensionState();
			}
			return list;
		}
		return null;
	}
	
	private void notify(String message) {
		this.notify(null, message, null);
	}
	
	private void notify(String message, SocketAddress address) {
		this.notify(null, message, address);
	}
	
	private void notify(String action, String message, SocketAddress address) {
		
		String mssg ="";
		
		if (action == null)
			action = "MESSAGE";
		
		mssg += action + " " + message + " ";
		if (address != null)
			mssg += "[ "+address.toString()+" ]";
		
		System.out.println(mssg);
		messageQueue.add(mssg);
	}

	@Override
	public String getMessages() {	
		return (String)messageQueue.poll();
	}

	@Override
	public RouteDetail[] getConnections() {
		RouteDetail[] routeArray = new RouteDetail[routes.size()];
		for(int i = 0; i < routes.size(); i++) {
			routeArray[i] = routes.get(i);
		}
		return routeArray;

	}

	@Override
	public ExtensionStates[] getExtensionStates() {
		
		int i = 0;
		ExtensionStates[] states = new ExtensionStates[16];
		for(Extension e : extensions)
		{
			states[i] = e.getExtensionState();
			i++;
		}
		return states;
	}
}